package com.mes.cloud.base.feign;

import java.lang.reflect.Type;
import java.util.Map;
import java.util.StringJoiner;
import java.util.stream.Stream;

import com.mes.cloud.commons.CommonUtil;
import com.mes.cloud.commons.KV;
import feign.RequestTemplate;
import feign.codec.EncodeException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.cloud.netflix.feign.support.SpringEncoder;
import org.springframework.core.convert.ConversionService;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Type;
import java.util.Map;
import java.util.StringJoiner;
import java.util.stream.Stream;

import static com.mes.cloud.base.feign.FormParamParameterProcessor.AUTO_NAME_PREFIX;
import static com.mes.cloud.base.feign.FormParamParameterProcessor.POST;

//Don't use this with bodyIndex
public class DefaultEncoder extends SpringEncoder {

    ConversionService conversionService;

    public DefaultEncoder(ObjectFactory<HttpMessageConverters> messageConverters,
                          ConversionService conversionService) {
        super(messageConverters);
        this.conversionService = conversionService;
    }

    @Override
    public void encode(Object requestBody, Type bodyType, RequestTemplate request) throws EncodeException {
        if (!MAP_STRING_WILDCARD.equals(bodyType) || !POST.equalsIgnoreCase(request.method())
                || request.bodyTemplate() != null) {
            super.encode(requestBody, bodyType, request);
            return;
        }

        Map<String, Object> map = (Map<String, Object>) requestBody;
        StringJoiner template = new StringJoiner("&");
        map.forEach((name, value) -> {
            if (value != null) {
                if (!name.startsWith(AUTO_NAME_PREFIX)) {
                    addParam(template, name, value);
                } else if (Map.class.isInstance(value)) {
                    Map<String, Object> valueMap = (Map<String, Object>) value;
                    valueMap.forEach((k, v) -> addParam(template, k, v));
                } else {
                    Stream.of(BeanUtils.getPropertyDescriptors(value.getClass()))
                            .filter(pd -> pd.getReadMethod() != null && !pd.getName().equals("class"))
                            .flatMap(pd -> Stream.of(KV.of(pd.getName(),
                                    ReflectionUtils.invokeMethod(pd.getReadMethod(), value))))
                            .forEach(kv -> addParam(template, kv.getK(), kv.getV()));
                }
            }
        });

        request.bodyTemplate(template.toString());
    }

    private void addParam(StringJoiner joiner, String name, Object value) {
        String stringValue = conversionService.convert(value, String.class);
        if (StringUtils.hasText(stringValue)) {
            //twice urlEncoder for RequestTemplate urlDecode(bodyTemplate)
            joiner.add(String.format("%s=%s", name, CommonUtil.urlEncoder(CommonUtil.urlEncoder(stringValue))));
        }
    }
}
